package cn.iocoder.yudao.framework.web.controller.error;

import jakarta.annotation.Nonnull;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;

/**
 * <pre>
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 *  统一异常信息的构建工具：获取错误/异常、通信状态、堆栈轨迹
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * </pre>
 *
 * @author 山野羡民（1032694760@qq.com）
 */
@Setter
@Getter
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ErrorInfoBuilder implements HandlerExceptionResolver, Ordered {
    private final static String ERROR_NAME = "custom-error";
    private final ServerProperties serverProperties;

    public ErrorInfoBuilder(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    public ErrorInfoVO getErrorInfo(HttpServletRequest request) {
        return getErrorInfo(request, getError(request));
    }

    public ErrorInfoVO getErrorInfo(HttpServletRequest request, Throwable error) {
        ErrorInfoVO errorInfoVO = new ErrorInfoVO();
        errorInfoVO.setTime(LocalDateTime.now().toString());
        errorInfoVO.setMethod(request.getMethod());
        errorInfoVO.setUrl(request.getRequestURL().toString());
        errorInfoVO.setQuery(request.getQueryString());
        errorInfoVO.setError(error);
        errorInfoVO.setStatusCode(getHttpStatus(request).value());
        errorInfoVO.setReasonPhrase(getHttpStatus(request).getReasonPhrase());
        errorInfoVO.setStackTrace(getStackTraceInfo(error, isIncludeStackTrace(request)));
        return errorInfoVO;
    }

    /**
     * @see DefaultErrorAttributes #addErrorDetails
     */
    public Throwable getError(HttpServletRequest request) {
        //根据HandlerExceptionResolver接口方法来获取错误.
        Throwable error = (Throwable) request.getAttribute(ERROR_NAME);
        //根据Request对象获取错误.
        if (error == null) {
            error = (Throwable) request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE);
        }
        //当获取错误非空,取出RootCause.
        if (error != null) {
            while (error instanceof ServletException && error.getCause() != null) {
                error = error.getCause();
            }
        }
        //当获取错误为null,此时我们设置错误信息即可.
        else {
            String message = (String) request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE);
            if (StringUtils.isEmpty(message)) {
                HttpStatus status = getHttpStatus(request);
                message = "Unknown Exception But " + status.value() + " " + status.getReasonPhrase();
            }
            error = new Exception(message);
        }
        return error;
    }

    /**
     * @see AbstractErrorController #getStatus
     */
    public HttpStatus getHttpStatus(HttpServletRequest request) {
        try {
            Integer statusCode = (Integer) request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);
            return statusCode != null ? HttpStatus.valueOf(statusCode) : HttpStatus.EXPECTATION_FAILED;
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

    /**
     * @see DefaultErrorAttributes #addStackTrace
     */
    public String getStackTraceInfo(Throwable error, boolean flag) {
        if (!flag) {
            return "omitted";
        }
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        return stackTrace.toString();
    }

    /**
     * @see BasicErrorController #isIncludeStackTrace
     */
    public boolean isIncludeStackTrace(HttpServletRequest request) {
        ErrorProperties errorProperties = serverProperties.getError();
        if (errorProperties == null) {
            return false;
        }
        //情况1：若IncludeStacktrace为ALWAYS
        if (errorProperties.getIncludeStacktrace() == ErrorProperties.IncludeAttribute.ALWAYS) {
            return true;
        }
        //情况2：若请求参数含有trace
        if (errorProperties.getIncludeStacktrace() == ErrorProperties.IncludeAttribute.ON_PARAM) {
            String parameter = request.getParameter("trace");
            return parameter != null && !"false".equalsIgnoreCase(parameter);
        }
        //情况3：其它情况
        return false;
    }

    /**
     * @see DispatcherServlet #processHandlerException 进行选举HandlerExceptionResolver
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, @Nonnull HttpServletResponse response, @Nullable Object handler, @Nonnull Exception ex) {
        request.setAttribute(ERROR_NAME, ex);
        return null;
    }

    /**
     * 提供优先级 或用于排序
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

}
